﻿using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;

namespace AhpilyServer
{
    /// <summary>
    /// 封装的客户端
    /// </summary>
    public class ClientPeer
    {
        public Socket ClientSocket { get; set; }

        public ClientPeer()
        {
            this.ReceiveArgs = new SocketAsyncEventArgs();
            this.ReceiveArgs.UserToken = this;
            this.ReceiveArgs.SetBuffer(new byte[1024], 0, 1024);
            this.SendArgs = new SocketAsyncEventArgs();
            this.SendArgs.Completed += SendArgs_Completed;
        }


        /// <summary>
        /// 设置连接对象
        /// </summary>
        /// <param name="socket"></param>
        public void SetSocket(Socket socket)
        {
            this.ClientSocket = socket;
        }

        #region 接收数据

        public delegate void ReceiveCompleted(ClientPeer client, SocketMsg Msg);

        /// <summary>
        /// 一个消息解析完成的回调
        /// </summary>
        public ReceiveCompleted receiveCompleted;

        /// <summary>
        /// 一旦接收到数据 就存到缓存区
        /// </summary>
        private List<byte> dataCache = new List<byte>();

        /// <summary>
        /// 接收的异步套接字请求
        /// </summary>
        public SocketAsyncEventArgs ReceiveArgs { set; get; }

        /// <summary>
        /// 是否正在处理接收的数据
        /// </summary>
        private bool isReceiveProcess;

        /// <summary>
        /// 自身处理数据包
        /// </summary>
        /// <param name="packet"></param>
        public void StartReceive(byte[] packet)
        {
            dataCache.AddRange(packet);

            if (!isReceiveProcess)
            {
                ProcessReceive();
            }
        }

        /// <summary>
        /// 处理接收的数据
        /// </summary>
        private void ProcessReceive()
        {
            isReceiveProcess = true;
            // 解析数据包
            byte[] data = EncodeTool.DecodePacket(ref dataCache);

            if (data == null)
            {
                isReceiveProcess = false;
                return;
            }

            // TODO: 需要再次转成 一个具体的类型 供我们使用
            SocketMsg msg = EncodeTool.DecodeMsg(data);

            // 回调给上层
            if (receiveCompleted != null)
            {
                receiveCompleted(this, msg);
            }

            // 尾递归
            ProcessReceive();
        }

        #region 粘包拆包问题

        // 粘包拆包问题： 
        // 解决决策： 1.添加消息头和消息尾
        // 2. 消息定长 
        // 3. 类似FTP(简单文件传输协议）添加空格或换行符(特殊字符）

        void Test()
        {
            byte[] bt = Encoding.Default.GetBytes("1234");
            // 怎么构造
            // 头： 消息的长度 bt.Length
            // 尾： 具体的消息

            int length = bt.Length;
            byte[] bt1 = BitConverter.GetBytes(length);

            // bt1 + bt


            // 整么读取
            // 前4个字节 32位 转成 Int32
            // 再根据读取值读取消息
        }

        #endregion

        #endregion

        #region 断开连接

        /// <summary>
        /// 断开连接
        /// </summary>
        public void Disconnect()
        {
            try
            {
                // 清空数据
                dataCache.Clear();
                isReceiveProcess = false;
                // 给发送数据预留的
                sendQueue.Clear();
                isSendProcess = false;

                ClientSocket.Shutdown(SocketShutdown.Both);
                ClientSocket.Close();
                ClientSocket = null;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        #endregion

        #region 发送数据

        /// <summary>
        /// 发送的消息的队列
        /// </summary>
        private Queue<byte[]> sendQueue = new Queue<byte[]>();

        /// <summary>
        /// 是否正在忙于发送消息
        /// </summary>
        private bool isSendProcess;

        /// <summary>
        /// 发送的异步套接字操作
        /// </summary>
        private SocketAsyncEventArgs SendArgs;

        /// <summary>
        /// 发送的时候 发现断开连接的回调
        /// </summary>
        /// <param name="client"></param>
        /// <param name="reason"></param>
        public delegate void SendDisconnect(ClientPeer client, string reason);

        public SendDisconnect sendDisconnect;

        /// <summary>
        /// 发送网络消息
        /// </summary>
        /// <param name="opCode">操作码</param>
        /// <param name="subCode">子操作</param>
        /// <param name="value">参数</param>
        public void Send(int opCode, int subCode, object value)
        {
            SocketMsg msg = new SocketMsg(opCode, subCode, value);

            byte[] data = EncodeTool.EncodeMsg(msg);
            byte[] packet = EncodeTool.EncodePacket(data);

            Send(packet);

            //// 存入消息队列
            //sendQueue.Enqueue(packet);

            //if(!isSendProcess)
            //{
            //    processSend();
            //}
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="packet"></param>
        public void Send(byte[] packet)
        {
            sendQueue.Enqueue(packet);
            if (!isSendProcess)
                send();
        }

        /// <summary>
        /// 处理发送的消息
        /// </summary>
        private void send()
        {
            isSendProcess = true;

            if (sendQueue.Count == 0)
            {
                isSendProcess = false;
                return;
            }

            // 从需要发送的消息队列取出一个准备发送
            byte[] packet = sendQueue.Dequeue();

            // 设置消息发送异步对象的发送数据缓冲区
            SendArgs.SetBuffer(packet, 0, packet.Length);
            bool result = ClientSocket.SendAsync(SendArgs);

            if (result == false)
            {
                processSend();
            }
        }

        private void SendArgs_Completed(object sender, SocketAsyncEventArgs e)
        {
            processSend();
        }

        /// <summary>
        /// 当异步发送请求完成的时候调用
        /// </summary>
        private void processSend()
        {
            // 发送的有没有错误
            if (SendArgs.SocketError != SocketError.Success)
            {
                // 本次发送出错了 客户端断开连接了
                sendDisconnect(this, SendArgs.SocketError.ToString());
            }
            else
            {
                send();
            }
        }

        #endregion
    }
}